/**
 *
 */
package com.angel.io.processors.commands.impl;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.angel.common.converters.base.Converter;
import com.angel.common.helpers.AnnotationHelper;
import com.angel.common.helpers.ReflectionHelper;
import com.angel.io.annotations.ColumnRow;
import com.angel.io.annotations.RowChecker;
import com.angel.io.annotations.RowProcessor;
import com.angel.io.annotations.RowProcessorCommand;
import com.angel.io.exceptions.InvalidRowDataException;
import com.angel.io.exceptions.RowProcessorCommandValidatorException;
import com.angel.io.helpers.Helper;
import com.angel.io.parameters.ParametersService;
import com.angel.io.processors.commands.imports.ImportRowProcessorCommand;
import com.angel.io.type.rows.ImportRowFile;
import com.angel.io.type.rows.impl.HeaderRowFile;

/**
 * @author William
 *
 */
public class AnnotationImportRowProcessorCommand implements ImportRowProcessorCommand{

	private Object rowProcessorCommand;
	private Method methodRowProcessor;
	private Method methodRowChecker;

	public AnnotationImportRowProcessorCommand(Object rowProcessorCommand){
		super();
		this.setRowProcessorCommand(rowProcessorCommand);
		this.initializeMethodRowProcessor(rowProcessorCommand);
		this.initializeMethodRowCheker(rowProcessorCommand);
	}

    protected void initializeMethodRowProcessor(Object rowProcessorCommand) {
		Class<?> rowProcessorCommandClassType = rowProcessorCommand.getClass();
		Method methodRowProcessor = AnnotationHelper.getMethodWithAnnotation(rowProcessorCommandClassType, RowProcessor.class);
		this.setMethodRowProcessor(methodRowProcessor);
	}

    protected void initializeMethodRowCheker(Object rowProcessorCommand) {
		Class<?> rowProcessorCommandClassType = rowProcessorCommand.getClass();
		Method methodRowChecker = AnnotationHelper.getMethodWithAnnotation(rowProcessorCommandClassType, RowChecker.class);
		this.setMethodRowChecker(methodRowChecker);
	}

    protected boolean hasMethodRowChecker(){
    	return this.getMethodRowChecker() != null;
    }

	public void checkRowData(ImportRowFile importRowFile, HeaderRowFile headerRowFile) throws InvalidRowDataException {
		if(this.hasMethodRowChecker()){
			RowChecker rowChecker = this.getRowCheckerAnnotation();
			int quantityParameters = this.calculateQuantityParameters(rowChecker);

			Object[] parameters = new Object[quantityParameters];
			int currentPosition = 0;
			if(rowChecker.importRow()){
				parameters[currentPosition] = importRowFile;
				currentPosition++;
			}
			if(rowChecker.headerRow()){
				parameters[currentPosition] = headerRowFile;
				currentPosition++;
			}
			if(rowChecker.columnsParameters().length == 0){
				if(importRowFile.quantityColumns() > quantityParameters){
					throw new RowProcessorCommandValidatorException("Import row file quantity columns [" +
							importRowFile.quantityColumns() + "] mustn't be more bigger than row check method quantity parameters [" + quantityParameters + "] at [" + rowProcessorCommand.getClass() + "]. " +
							"It may be caused by: \n" +
							"- You have a row processor command differents with yours file in input file stream builder.");
				}

				for(int i = 0; i < importRowFile.quantityColumns(); i++ ){
					Object o = Converter.convert(importRowFile.getValue(i).getClass(), importRowFile.getValue(i), this.getRowProcessorCommandAnnotation().columnsRow()[i].type());
					parameters[currentPosition] = o;
					currentPosition++;
				}
			} else {
				for(int i :rowChecker.columnsParameters()){
					Object o = Converter.convert(importRowFile.getValue(i - 1).getClass(), importRowFile.getValue(i - 1), this.getRowProcessorCommandAnnotation().columnsRow()[i].type());
					parameters[currentPosition] = o;
					currentPosition++;
				}
			}



			try {
				this.getMethodRowChecker().invoke(this.getRowProcessorCommand(), parameters);
			} catch (IllegalArgumentException e) {
				// TODO Change message exception.
				throw new RowProcessorCommandValidatorException("Error invoking checker method [" + this.getMethodRowChecker().getName() + "] at " +
						"object class [" + this.getRowProcessorCommand().getClass() + "].", e);
			} catch (IllegalAccessException e) {
				throw new RowProcessorCommandValidatorException("Error invoking checker method [" + this.getMethodRowChecker().getName() + "] at " +
						"object class [" + this.getRowProcessorCommand().getClass() + "].", e);
			} catch (InvocationTargetException e) {
				throw new RowProcessorCommandValidatorException("Error invoking checker method [" + this.getMethodRowChecker().getName() + "] at " +
						"object class [" + this.getRowProcessorCommand().getClass() + "].", e);
			}
		}
    }

	/**TODO chequear: cantidad y tipo de parametros.
	 * OK. tipo de retorno del metodo.
	 * Que el object tenga constructor default.
	 *
	 */
	@SuppressWarnings("unchecked")
	public <T> T processRow(ImportRowFile importRowFile, HeaderRowFile headerRowFile, ParametersService parametersService) {
		RowProcessor rowProcessor = this.getRowProcessorAnnotation();
		int quantityParameters = this.calculateQuantityParameters(rowProcessor);
		Object[] parameters = new Object[quantityParameters];
		int currentPosition = 0;
		if(rowProcessor.importRow()){
			parameters[currentPosition] = importRowFile;
			currentPosition++;
		}
		if(rowProcessor.headerRow()){
			parameters[currentPosition] = headerRowFile;
			currentPosition++;
		}
		if(!rowProcessor.object().equals(Object.class)){
			Object domainObject = ReflectionHelper.createObject(rowProcessor.object());
			parameters[currentPosition] = domainObject;
			if(rowProcessor.inject()){
				this.injectAttributesTo(domainObject, rowProcessor, this.getRowProcessorCommandAnnotation(), importRowFile, headerRowFile);
			}
			currentPosition++;
		}
		if(rowProcessor.columnsParameters().length == 0){
			for(int i = 0; i < importRowFile.quantityColumns(); i++ ){
				Object o = Converter.convert(importRowFile.getValue(i).getClass(), importRowFile.getValue(i), this.getRowProcessorCommandAnnotation().columnsRow()[i].type());
				parameters[currentPosition] = o;
				currentPosition++;
			}
		} else {
			for(int i :rowProcessor.columnsParameters()){
				Object o = Converter.convert(importRowFile.getValue(i - 1).getClass(), importRowFile.getValue(i - 1), this.getRowProcessorCommandAnnotation().columnsRow()[i].type());
				parameters[currentPosition] = o;
				currentPosition++;
			}
		}

		Object returnObject = null;
		try {
			returnObject = this.getMethodRowProcessor().invoke(this.getRowProcessorCommand(), parameters);
		} catch (IllegalArgumentException e) {
			// TODO Change message exception.
			throw new RowProcessorCommandValidatorException("Error invoking row processor method [" + this.getMethodRowProcessor().getName() +
					"] at class [" + this.getRowProcessorCommand().getClass().getName() + "].", e);
		} catch (IllegalAccessException e) {
			// TODO Change message exception.
			throw new RowProcessorCommandValidatorException("Error invoking row processor method [" + this.getMethodRowProcessor().getName() +
					"] at class [" + this.getRowProcessorCommand().getClass().getName() + "].", e);
		} catch (InvocationTargetException e) {
			// TODO Change message exception.
			throw new RowProcessorCommandValidatorException("Error invoking row processor method [" + this.getMethodRowProcessor().getName() +
					"] at class [" + this.getRowProcessorCommand().getClass().getName() + "].", e);
		}
		return (T) returnObject;
    }


	private void injectAttributesTo(Object domainObject, RowProcessor rowProcessor, RowProcessorCommand rowProcessorCommandAnnotation, ImportRowFile importRowFile, HeaderRowFile headerRowFile) {
		int pos = 0;
		for(ColumnRow cr: rowProcessorCommandAnnotation.columnsRow()){
			String attributeName = cr.columnName();
			Field attributeField = ReflectionHelper.getFieldOrNullByNameInInherit(domainObject.getClass(), attributeName);
			if(attributeField != null){
				boolean accesible = attributeField.isAccessible();
				attributeField.setAccessible(true);
				boolean isPrimitiveType = ReflectionHelper.isAJavaPrimitiveClass(attributeField.getType());
				if(isPrimitiveType){
					Object o = Converter.convert(importRowFile.getValue(pos).getClass(), importRowFile.getValue(pos), cr.type());
					ReflectionHelper.setFieldValue(domainObject, attributeField, o);
					attributeField.setAccessible(accesible);
				}
			} else {
				for(String aliasName: cr.aliases()){
					attributeField = ReflectionHelper.getFieldOrNullByNameInInherit(domainObject.getClass(), aliasName);
					if(attributeField != null){
						boolean isPrimitiveType = ReflectionHelper.isAJavaPrimitiveClass(attributeField.getType());
						if(isPrimitiveType){
							Object o = Converter.convert(importRowFile.getValue(pos).getClass(), importRowFile.getValue(pos), cr.type());
							ReflectionHelper.setFieldValue(domainObject, attributeField, o);
						}
					}
				}
			}
			pos++;
		}

	}

	protected int calculateQuantityParameters(RowChecker rowChecker){
		return Helper.calculateQuantityParameters(rowChecker, this.getHeaderColumnsNames().length);
	}

	protected int calculateQuantityParameters(RowProcessor rowProcessor){
		return Helper.calculateQuantityParameters(rowProcessor, this.getHeaderColumnsNames().length);
	}

	protected RowProcessorCommand getRowProcessorCommandAnnotation(){
		return AnnotationHelper.getAnnotation(this.getRowProcessorCommand().getClass(), RowProcessorCommand.class);
	}

	protected RowProcessor getRowProcessorAnnotation(){
		return AnnotationHelper.getAnnotation(this.getMethodRowProcessor(), RowProcessor.class);
	}

	protected RowChecker getRowCheckerAnnotation(){
		return AnnotationHelper.getAnnotation(this.getMethodRowChecker(), RowChecker.class);
	}

    public String[] getHeaderColumnsNames() {
    	RowProcessorCommand rowProcessorCommand = this.getRowProcessorCommandAnnotation();
    	ColumnRow[] columnsRows = rowProcessorCommand.columnsRow();
    	String[] headerColumns = new String[columnsRows.length];
    	int i = 0;
    	for(ColumnRow columnRow: columnsRows){
    		headerColumns[i] = columnRow.columnName();
    		i++;
    	}
        return headerColumns;
    }

	/**
	 * @return the rowProcessorCommand
	 */
	public Object getRowProcessorCommand() {
		return rowProcessorCommand;
	}

	/**
	 * @param rowProcessorCommand the rowProcessorCommand to set
	 */
	protected void setRowProcessorCommand(Object rowProcessorCommand) {
		this.rowProcessorCommand = rowProcessorCommand;
	}

	/**
	 * @return the methodRowProcessor
	 */
	protected Method getMethodRowProcessor() {
		return methodRowProcessor;
	}

	/**
	 * @param methodRowProcessor the methodRowProcessor to set
	 */
	protected void setMethodRowProcessor(Method methodRowProcessor) {
		this.methodRowProcessor = methodRowProcessor;
	}

	/**
	 * @return the methodRowChecker
	 */
	protected Method getMethodRowChecker() {
		return methodRowChecker;
	}

	/**
	 * @param methodRowChecker the methodRowChecker to set
	 */
	protected void setMethodRowChecker(Method methodRowChecker) {
		this.methodRowChecker = methodRowChecker;
	}
}
